using System;
using System.Collections;
using System.Collections.Generic;

namespace Kopernikus.Collections.ThreadSafe
{
    public class ThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        private readonly object SyncRoot = new object();
        private object m_data;
        private Dictionary<TKey, TValue> m_dict;

        public ThreadSafeDictionary()
        {
            m_dict = new Dictionary<TKey, TValue>();
        }

        public ThreadSafeDictionary(IDictionary<TKey, TValue> dictionary)
        {
            m_dict = new Dictionary<TKey, TValue>(dictionary);
        }

        public ThreadSafeDictionary(IEqualityComparer<TKey> comparer)
        {
            m_dict = new Dictionary<TKey, TValue>(comparer);
        }

        public ThreadSafeDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
        {
            m_dict = new Dictionary<TKey, TValue>(dictionary, comparer);
        }

        public ThreadSafeDictionary(int capacity, IEqualityComparer<TKey> comparer)
        {
            m_dict = new Dictionary<TKey, TValue>(capacity, comparer);
        }

        #region IDictionary<TKey,TValue> Members

        public void Add(TKey key, TValue value)
        {
            lock (SyncRoot)
            {
                Dictionary<TKey, TValue> newDict = CopyDict();
                newDict.Add(key, value);
                SwitchDict(newDict);
            }
        }

        public bool ContainsKey(TKey key)
        {
            return m_dict.ContainsKey(key);
        }

        public ICollection<TKey> Keys
        {
            get { return m_dict.Keys; }
        }

        public bool Remove(TKey key)
        {
            bool result;
            lock (SyncRoot)
            {
                Dictionary<TKey, TValue> newDict = CopyDict();
                result = newDict.Remove(key);
                SwitchDict(newDict);
            }
            return result;
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            return m_dict.TryGetValue(key, out value);
        }

        public ICollection<TValue> Values
        {
            get { return m_dict.Values; }
        }

        public TValue this[TKey key]
        {
            get { return m_dict[key]; }
            set
            {
                lock (SyncRoot)
                {
                    Dictionary<TKey, TValue> newDict = CopyDict();
                    newDict[key] = value;
                    SwitchDict(newDict);
                }
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            lock (SyncRoot)
            {
                Dictionary<TKey, TValue> newDict = CopyDict();
                ((IDictionary<TKey, TValue>) newDict).Add(item);
                SwitchDict(newDict);
            }
        }

        public void Clear()
        {
            lock (SyncRoot)
            {
                SwitchDict(CopyEmptyDict());
            }
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return ((IDictionary<TKey, TValue>) m_dict).Contains(item);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            ((IDictionary<TKey, TValue>) m_dict).CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return m_dict.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            bool result;
            lock (SyncRoot)
            {
                Dictionary<TKey, TValue> newDict = CopyDict();
                result = ((IDictionary<TKey, TValue>) newDict).Remove(item);
                SwitchDict(newDict);
            }
            return result;
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return m_dict.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return m_dict.GetEnumerator();
        }

        #endregion

        private Dictionary<TKey, TValue> CopyEmptyDict()
        {
            return new Dictionary<TKey, TValue>(m_dict.Comparer);
        }

        private Dictionary<TKey, TValue> CopyDict()
        {
            return new Dictionary<TKey, TValue>(m_dict, m_dict.Comparer);
        }

        private void SwitchDict(Dictionary<TKey, TValue> dict)
        {
            m_dict = dict;
            m_data = null;
        }

        public TValue GetValue(TKey key, Converter<TKey, TValue> construct)
        {
            TValue value;
            if (!m_dict.TryGetValue(key, out value))
            {
                lock (SyncRoot)
                {
                    if (!m_dict.TryGetValue(key, out value))
                    {
                        value = construct(key);
                        Dictionary<TKey, TValue> newDict = CopyDict();
                        newDict.Add(key, value);
                        SwitchDict(newDict);
                    }
                }
            }
            return value;
        }

        public object GetData(Converter<ThreadSafeDictionary<TKey, TValue>, object> construct)
        {
            object data = m_data;
            if (data == null)
            {
                lock (SyncRoot)
                {
                    data = m_data;
                    if (data == null)
                    {
                        data = m_data = construct(this);
                    }
                }
            }
            return data;
        }

        public void SetData(object data)
        {
            m_data = data;
        }
    }
}